package droideep.library.widget;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.ScrollView;

import droideep.library.R;
import droideep.library.utils.DLog;
import droideep.library.utils.MetricsConverter;

/**
 * TODO: document your custom view class.
 */
public class FloatingActionButton extends ImageView {

    private static final String FAB_LOG_TAG = "FloatingActionButton";
    private Type type = Type.NORMAL;

    private int mFabColor;

    private float mShadowRadius = MetricsConverter.dpToPx(getContext(), 2.0f);
    private float mShadowXOffset = MetricsConverter.dpToPx(getContext(), 1.0f);
    private float mShadowYOffset = MetricsConverter.dpToPx(getContext(), 1.5f);
    private int mShadowColor = Color.parseColor("#757575");

    private Animation mShowAnimation;
    private Animation mHideAnimation;

    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    private CircleShadowDrawable mDrawable;

    private int mScrollThreshold;

    public FloatingActionButton(Context context) {
        this(context, null);
    }

    public FloatingActionButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initFloatingActionButton(context, attrs, defStyleAttr, 0);
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public FloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initFloatingActionButton(context, attrs, defStyleAttr, defStyleRes);
    }

    private void initFloatingActionButton(Context context, AttributeSet attrs, int defStyle, int defStyleRes) {
//        initTouchMode();
        initLayerType();
        TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.FloatingActionButton, defStyle, defStyleRes);
        initType(attributes);
        initFabColor(attributes);
        initShadowRadius(attributes);
        initShadowXOffset(attributes);
        initShadowYOffset(attributes);
        initShadowColor(attributes);
        initShowAnimation(attributes);
        initHideAnimation(attributes);
        initScrollThreshold();

        attributes.recycle();
    }

    private void initTouchMode() {
        setFocusable(true);
        setClickable(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(calculateMeasuredWidth(), calculateMeasuredHeight());
        DLog.v(FAB_LOG_TAG, String.format("Fab size measure width:height = %s , %s", getWidth(), getHeight()));
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        drawCircle(canvas);

    }

    private void drawCircle(Canvas canvas) {
        mDrawable = new CircleShadowDrawable(mFabColor, getShadowRadius(), getShadowXOffset(), getShadowYOffset(), getShadowColor());
        mDrawable.draw(canvas);
    }

    private void resetPaint() {
        mPaint.reset();
        mPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
    }

    private void drawShadow() {
        mPaint.setShadowLayer(getShadowRadius(), getShadowXOffset(), getShadowYOffset(), getShadowColor());
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void initLayerType() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
    }

    private void initType(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_type)) {
            int id = attributes.getInt(R.styleable.FloatingActionButton_fab_type, type.getId());
            type = Type.forId(id);
        }
    }

    private void initFabColor(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_color)) {
            mFabColor = attributes.getColor(R.styleable.FloatingActionButton_fab_color, R.color.red);
        }
    }

    private void initShadowRadius(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_shadowRadius)) {
            mShadowRadius = attributes.getDimension(R.styleable.FloatingActionButton_fab_shadowRadius, mShadowRadius);
        }
    }

    private void initShadowXOffset(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_shadowXOffset)) {
            mShadowXOffset = attributes.getDimension(R.styleable.FloatingActionButton_fab_shadowXOffset, mShadowXOffset);
        }
    }

    private void initShadowYOffset(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_shadowYOffset)) {
            mShadowYOffset = attributes.getDimension(R.styleable.FloatingActionButton_fab_shadowYOffset, mShadowYOffset);
        }
    }

    private void initShadowColor(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_shadowColor)) {
            mShadowColor = attributes.getColor(R.styleable.FloatingActionButton_fab_shadowColor, mShadowColor);
        }
    }

    private void initShowAnimation(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_showAnimation)) {
            final int animResId = attributes.getResourceId(R.styleable.FloatingActionButton_fab_showAnimation
                    , Animations.NONE.animResId);
            mShowAnimation = Animations.load(getContext(), animResId);
            DLog.v(FAB_LOG_TAG, "Initialized animation on show");
        }
    }

    private void initHideAnimation(TypedArray attributes) {
        if (attributes.hasValue(R.styleable.FloatingActionButton_fab_hideAnimation)) {
            final int animResId = attributes.getResourceId(R.styleable.FloatingActionButton_fab_hideAnimation
                    , Animations.NONE.animResId);
            mHideAnimation = Animations.load(getContext(), animResId);
            DLog.v(FAB_LOG_TAG, "Initialized animation on hide");
        }
    }

    private void initScrollThreshold() {
        mScrollThreshold = getResources().getDimensionPixelOffset(R.dimen.fab_scroll_threshold);
    }

    public Animation getShowAnimation() {
        return mShowAnimation;
    }

    public void startAnimation(Animation animation) {
        if (animation != null) {
            super.startAnimation(animation);
        }
    }

    public void setShowAnimation(Animations animation) {
        this.mShowAnimation = Animations.load(getContext(), animation.animResId);
    }

    public Animation getHideAnimation() {
        return mHideAnimation;
    }

    public void setHideAnimation(Animation hideAnimation) {
        this.mHideAnimation = hideAnimation;
    }

    public void setHideAnimation(Animations animation) {
        this.mHideAnimation = Animations.load(getContext(), animation.animResId);
    }

    public void removeShowAnimation() {
        setShowAnimation(Animations.NONE);
    }

    /**
     * Makes the <b>Floating Action Button</b> to appear and
     * sets its visibility to {@link #VISIBLE}
     * <p>
     * {@link #mShowAnimation} is played if set
     */
    public void show() {
        if (isHidden()) {
            playShowAnimation();
            setVisibility(VISIBLE);
            DLog.v(FAB_LOG_TAG, "Fab show");
        }
    }

    /**
     * Makes the <b>Floating Action Button</b> to disappear and
     * sets its visibility to {@link #INVISIBLE}
     * <p>
     * {@link #mHideAnimation} is played if set
     */
    public void hide() {
        if (!isHidden() && !isDismissed()) {
            playHideAnimation();
            setVisibility(INVISIBLE);
            DLog.v(FAB_LOG_TAG, "Fab hide");
        }
    }

    public boolean hasShadow() {
        return getShadowRadius() > 0.0f;
    }

    private int calculateMeasuredWidth() {
        return getButtonSize() + calculateShadowWidth();
    }

    private int calculateMeasuredHeight() {
        return getButtonSize() + calculateShadowHeight();
    }

    public int getButtonSize() {
        return type.getSize(getContext());
    }

    protected float calculateCenterX() {
        final float centerX = getMeasuredWidth() / 2;
        return centerX;
    }

    protected float calculateCenterY() {
        final float centerY = getMeasuredHeight() / 2;
        return centerY;
    }


    private int calculateShadowWidth() {
        final int shadowWidth = hasShadow() ? (int) ((getShadowRadius() + Math.abs(getShadowXOffset())) * 2) : 0;
        return shadowWidth;
    }


    private int calculateShadowHeight() {
        final int shadowHeight = hasShadow() ? (int) ((getShadowRadius() + Math.abs(getShadowYOffset())) * 2) : 0;
        return shadowHeight;
    }

    private float calculateCircleRadius() {
        return getButtonSize() / 2;
    }

    public Type getType() {
        return type;
    }

    /**
     * Set the type of the <b>Floating Action Button</b>
     * call fab's parent to invalidate the layout of view
     *
     * @param type
     */
    public void setType(Type type) {
        this.type = type;
        requestLayout();
    }

    public float getShadowRadius() {
        return mShadowRadius;
    }

    public void setShadowRadius(float mShadowRadius) {
        this.mShadowRadius = MetricsConverter.dpToPx(getContext(), mShadowRadius);
        requestLayout();
    }

    public float getShadowXOffset() {
        return mShadowXOffset;
    }

    public void setShadowXOffset(float mShadowXOffset) {
        this.mShadowXOffset = MetricsConverter.dpToPx(getContext(), mShadowXOffset);
        requestLayout();
    }

    public float getShadowYOffset() {
        return mShadowYOffset;
    }

    public void setShadowYOffset(float mShadowYOffset) {
        this.mShadowYOffset = MetricsConverter.dpToPx(getContext(), mShadowYOffset);
        requestLayout();
    }

    public int getShadowColor() {
        return mShadowColor;
    }

    /**
     * Checks whether <b>Floating Action Button</b> is hidden
     *
     * @return true if <b>Floating Action Button</b> is hidden,otherwise false
     */
    public boolean isHidden() {
        return this.getVisibility() == INVISIBLE;
    }

    /**
     * Checks whether <b>Floating Action Button</b> is dismissed
     *
     * @return true if <b>Floating Action Button</b> is dismissed,otherwise false
     */
    public boolean isDismissed() {
        ViewGroup parent = (ViewGroup) getParent();
        return parent == null;
    }

    /**
     * Plays the {@link #mShowAnimation} if set
     */
    public void playShowAnimation() {
        startAnimation(getShowAnimation());
    }

    /**
     * Plays the {@link #mHideAnimation} if set
     */
    public void playHideAnimation() {
        startAnimation(getHideAnimation());
    }

//    public void setShadowColor(int mShadowColor) {
//        this.mShadowColor = mShadowColor;
//        invalidate();
//    }

    public void attachToScrollView(@NonNull ObservableScrollView scrollView) {
        attachToScrollView(scrollView, null, null);
    }

    public void attachToScrollView(@NonNull ObservableScrollView scrollView,
                                   ScrollDirectionListener scrollDirectionListener) {
        attachToScrollView(scrollView, scrollDirectionListener, null);
    }

    public void attachToScrollView(@NonNull ObservableScrollView scrollView,
                                   ScrollDirectionListener scrollDirectionListener,
                                   ObservableScrollView.OnScrollChangedListener onScrollChangedListener) {
        ScrollViewScrollDetectorImpl scrollDetector = new ScrollViewScrollDetectorImpl();
        scrollDetector.setScrollDirectionListener(scrollDirectionListener);
        scrollDetector.setOnScrollChangedListener(onScrollChangedListener);
        scrollDetector.setScrollThreshold(mScrollThreshold);
        scrollView.setOnScrollChangedListener(scrollDetector);
    }

    /**
     * Determines the <b>Floating Action Button</b> types
     */
    public enum Type {

        NORMAL {
            @Override
            int getId() {
                return 0;
            }

            @Override
            int getSize(Context context) {
                return (int) MetricsConverter.dpToPx(context, 56.0f);
            }

        },
        MINI {
            @Override
            int getId() {
                return 1;
            }

            @Override
            int getSize(Context context) {
                return (int) MetricsConverter.dpToPx(context, 40.0f);
            }

        };


        abstract int getId();

        abstract int getSize(Context context);

        static Type forId(int id) {
            for (Type type : values()) {
                if (type.getId() == id) {
                    return type;
                }
            }
            return NORMAL;
        }
    }

    private class CircleShadowDrawable extends Drawable {

        private Paint mPaint;

        private int mOvalColor;
        private float mShadowRadius;
        private float mShadowXOffset;
        private float mShadowYOffset;
        private int mShadowColor;

        private CircleShadowDrawable(int ovalColor, float shadowRadius, float shadowXOffset, float shadowYOffset, int shadowColor) {
            mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
            mPaint.setStyle(Paint.Style.FILL);

            this.mOvalColor = ovalColor;

            this.mShadowRadius = shadowRadius;
            this.mShadowXOffset = shadowXOffset;
            this.mShadowYOffset = shadowYOffset;
            this.mShadowColor = shadowColor;
        }

        @Override
        public void draw(Canvas canvas) {
            mPaint.setShadowLayer(mShadowRadius, mShadowXOffset, mShadowYOffset, mShadowColor);
            mPaint.setColor(mOvalColor);
            canvas.drawCircle(calculateCenterX(), calculateCenterY(), calculateCircleRadius(), mPaint);
        }

        @Override
        public void setAlpha(int alpha) {
            mPaint.setAlpha(alpha);
        }

        @Override
        public void setColorFilter(ColorFilter cf) {
            mPaint.setColorFilter(cf);
        }

        @Override
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
    }

    public enum Animations {
        /**
         * NONE. Animation absent
         */
        NONE(0),

        /**
         * Scale up animation
         */
        SCALE_UP(R.anim.fab_scale_up),

        /**
         * Scale down animation
         */
        SCALE_DOWN(R.anim.fab_scale_down);

        /**
         * Correspondent animation resource id
         */
        final int animResId;

        Animations(int animResId) {
            this.animResId = animResId;
        }

        protected static Animation load(Context context, int animResId) {
            return AnimationUtils.loadAnimation(context, animResId);
        }
    }

    private class ScrollViewScrollDetectorImpl extends ScrollViewScrollDetector {

        private ScrollDirectionListener mScrollDirectionListener;

        private ObservableScrollView.OnScrollChangedListener mOnScrollChangedListener;

        private void setScrollDirectionListener(ScrollDirectionListener scrollDirectionListener) {
            mScrollDirectionListener = scrollDirectionListener;
        }

        public void setOnScrollChangedListener(ObservableScrollView.OnScrollChangedListener onScrollChangedListener) {
            mOnScrollChangedListener = onScrollChangedListener;
        }

        @Override
        void onScrollUp() {
            show();
            if (mScrollDirectionListener != null) {
                mScrollDirectionListener.onScrollUp();
            }
        }

        @Override
        void onScrollDown() {
            hide();
            if (mScrollDirectionListener != null) {
                mScrollDirectionListener.onScrollDown();
            }
        }

        @Override
        public void onScrollChanged(ScrollView who, int l, int t, int oldl, int oldt) {
            if (mOnScrollChangedListener != null) {
                mOnScrollChangedListener.onScrollChanged(who, l, t, oldl, oldt);
            }
            super.onScrollChanged(who, l, t, oldl, oldt);
        }
    }

}
